package com.leyou.search.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.leyou.common.enums.ExceptionEnum;
import com.leyou.common.exceptions.LyException;
import com.leyou.common.utils.BeanHelper;
import com.leyou.common.utils.JsonUtils;
import com.leyou.common.vo.PageResult;
import com.leyou.item.client.ItemClient;
import com.leyou.item.dto.*;
import com.leyou.search.dto.GoodsDTO;
import com.leyou.search.dto.SearchRequest;
import com.leyou.search.pojo.Goods;
import com.leyou.search.repositroy.GoodsRepository;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilterBuilder;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 处理请求和响应的方法
 *
 * @version V1.0
 * @package com.leyou.search.service
 * @description:
 * @author: pc
 * @date: 2019/6/30 18:17
 */

@Service("searchService")
public class SearchService {
    @Autowired
    private ItemClient itemClient;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private ElasticsearchTemplate esTemplate;



    public Goods buildGoods(SpuDTO spuDTO) {

        //1.所有需要被搜索的信息，包含名称，分类，品牌等等
        //1.1根据分类id查询分类名称
        String categoryName = itemClient.queryCategoryByIds(spuDTO.getCategoryIds())
                .stream().map(CategoryDTO::getName).
                        collect(Collectors.joining(","));
        //1.2根据品牌id查询品牌
        BrandDTO brandDTO = itemClient.queryBrandById(spuDTO.getBrandId());
        //1.3拼接字符串
        String all = spuDTO.getName()+categoryName+brandDTO.getName();

        //2.spu下的sku合集
        //2.1查询到所有sku数据
        List<SkuDTO> skuDTOS = itemClient.querySkuBySpuId(spuDTO.getId());
        //2.2因为只需要其中一部分数据，所以创建一个集合，里面放map，来承接sku的数据
        //sku本身是一个list集合，所以创建list集合
        //集合里存放的是键值对数据，所以在list中放一个map
        List<Map<String,Object>> skuList = new ArrayList<>();
        //2.3遍历集合，将sku数据中需要的数据添加到新的集合中
        for (SkuDTO skuDTO : skuDTOS) {
            HashMap<String, Object> skuMap = new HashMap<>();
            skuMap.put("skuId",skuDTO.getId());
            skuMap.put("title",skuDTO.getTitle());
            skuMap.put("price",skuDTO.getPrice());
            skuMap.put("image", StringUtils.substringBefore(skuDTO.getImages(),","));
            skuList.add(skuMap);
        }
        String skus = JsonUtils.toString(skuList);

        //3.sku的价格合集
        //3.1获取价格
        Set<Long> price = skuDTOS.stream().map(SkuDTO::getPrice).collect(Collectors.toSet());

        //4.搜索规格参数，包括参数名称，参数值
        Map<String, Object> specs = new HashMap<>();
        //4.1查询所有参数，以获得参数名称
        List<SpecParamDTO> specParamDTOS = itemClient.querySpecParams(null, spuDTO.getCid3(), true);
        //4.2 获取规格参数的值，来自于spuDetail
        SpuDetailDTO spuDetailDTO = itemClient.querySpuDetailById(spuDTO.getId());
        //4.3 获取通用参数
        Map<Long, Object> genericSpec = JsonUtils.toMap(spuDetailDTO.getGenericSpec(), Long.class, Object.class);
        //4.4 获取特殊参数
        Map<Long, List<String>> SpecialSpec = JsonUtils.nativeRead(spuDetailDTO.getSpecialSpec(), new TypeReference<Map<Long, List<String>>>() {
        });
        for (SpecParamDTO specParamDTO : specParamDTOS) {
            // 获取规格参数的名称
            String key = specParamDTO.getName();
            // 获取规格参数值
            Object value = null;
            // 判断是否是通用规格
            //获取参数名称的id去genericSpec或SpecialSpec中取值
            Long id = specParamDTO.getId();
            if (specParamDTO.getGeneric()) {
                // 通用规格
                value = genericSpec.get(id);
            } else {
                // 特有规格
                value = SpecialSpec.get(id);
            }

            if (specParamDTO.getNumeric()) {
                value = chooseSegment(value,specParamDTO);
            }
            specs.put(key,value);
        }


        //创建goods对象
        Goods goods = new Goods();
        goods.setSubTitle(spuDTO.getSubTitle());
        goods.setId(spuDTO.getId());
        goods.setCreateTime(spuDTO.getCreateTime().getTime());
        goods.setCategoryId(spuDTO.getCid3());
        goods.setBrandId(spuDTO.getBrandId());
        //所有需要被搜索的信息，包含标题，分类，甚至品牌
        goods.setAll(all);
        //spu下的sku合集
        goods.setSkus(skus);
        //sku的价格合集
        goods.setPrice(price);
        //搜索规格参数，包括参数名称，参数值
        goods.setSpecs(specs);

        return goods;
    }


    private String chooseSegment(Object value, SpecParamDTO p) {
        if (value == null || StringUtils.isBlank(value.toString())) {
            return "其它";
        }
        double val = parseDouble(value.toString());
        String result = "其它";
        // 保存数值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 获取数值范围
            double begin = parseDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if (segs.length == 2) {
                end = parseDouble(segs[1]);
            }
            // 判断是否在范围内
            if (val >= begin && val < end) {
                if (segs.length == 1) {
                    result = segs[0] + p.getUnit() + "以上";
                } else if (begin == 0) {
                    result = segs[1] + p.getUnit() + "以下";
                } else {
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

    private double parseDouble(String str) {
        try {
            return Double.parseDouble(str);
        } catch (Exception e) {
            return 0;
        }
    }


    /**
     * 页面数据分页查询
     * */
    public PageResult<GoodsDTO> search(SearchRequest request) {

        // 1.创建原生搜索构建器
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.组织条件
        // 2.0.source过滤，控制字段数量
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{"id", "subTitle", "skus"}, null));

        queryBuilder.withQuery(basicQueryBuild(request));
        // 2.2.分页条件
        int page = request.getPage() - 1;
        int size = request.getSize();
        queryBuilder.withPageable(PageRequest.of(page, size));

        // 3.搜索结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);

        // 4.解析结果
        // 4.1.解析分页数据
        long total = result.getTotalElements();
        int totalPage = result.getTotalPages();
        List<Goods> list = result.getContent();
        // 4.2.转换DTO
        List<GoodsDTO> dtoList = BeanHelper.copyWithCollection(list, GoodsDTO.class);
        // 5.封装并返回
        return new PageResult<>(total, totalPage, dtoList);
    }


    /**
     * 过滤条件栏的数据查询
     */
    public Map<String, List<?>> searchFliters(SearchRequest request) {
        Map<String,List<?>> filterList = new LinkedHashMap<>();
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        queryBuilder.withQuery(basicQueryBuild(request));
        //控制返回的结果
        queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{""},new String[0]));
        //分页条件
        queryBuilder.withPageable(PageRequest.of(0,1));

        //添加聚合条件
        //分类聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("categoryAgg").field("categoryId"));
        //品牌聚合
        queryBuilder.addAggregation(AggregationBuilders.terms("brandAgg").field("brandId"));
        //按照条件查询
        AggregatedPage<Goods> goods = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        //获取聚合
        Aggregations aggregations = goods.getAggregations();
        //品牌聚合
        Terms brandAgg = aggregations.get("brandAgg");
        //根据聚合中取出的ids查询品牌，并注入map中
        handleBrandAgg(brandAgg,filterList);
        //分类聚合
        Terms categoryAgg = aggregations.get("categoryAgg");
        //根据聚合中取出的ids查询分类，并注入map中
        List<Long> list = handleCategoryAgg(categoryAgg, filterList);
        if (list != null && list.size() == 1) {
            //调用方法处理规格数据
            handleSpecAgg(list.get(0),filterList,request);
        }

        return filterList;
    }

    private void handleSpecAgg(Long cid, Map<String, List<?>> filterList, SearchRequest request) {
        // 1.查询分类下需要搜索过滤的规格参数名称
        List<SpecParamDTO> params = itemClient.querySpecParams(null, cid, true);
        // 2.查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 2.1.获取查询条件
        queryBuilder.withQuery(basicQueryBuild(request));
        // 2.2.减少查询结果(这里只需要聚合结果)
        // 每页显示1个
        queryBuilder.withPageable(PageRequest.of(0, 1));
        // 显示空的source
        queryBuilder.withSourceFilter(new FetchSourceFilterBuilder().build());

        for (SpecParamDTO param : params) {
            // 获取param的name，作为聚合名称
            String name = param.getName();
            queryBuilder.addAggregation(AggregationBuilders.terms(name).field("specs." + name));
        }

        // 4.查询并获取结果
        AggregatedPage<Goods> result = esTemplate.queryForPage(queryBuilder.build(), Goods.class);
        Aggregations aggregations = result.getAggregations();

        // 5.解析聚合结果
        for (SpecParamDTO param : params) {
            // 获取param的name，作为聚合名称
            String name = param.getName();
            Terms terms = aggregations.get(name);
            // 获取聚合结果，注意，规格聚合的结果 直接是字符串，不用做特殊处理
            List<String> paramValues = terms.getBuckets()
                    .stream()
                    .map(Terms.Bucket::getKeyAsString)
                    // 过滤掉空的待选项
                    .filter(StringUtils::isNotEmpty)
                    .collect(Collectors.toList());
            // 存入map
            filterList.put(name, paramValues);
        }
    }


    private QueryBuilder basicQueryBuild(SearchRequest request) {
        // 健壮性判断
        String key = request.getKey();
        if (StringUtils.isBlank(key)) {
            throw new LyException(ExceptionEnum.GOODS_NOT_FOUND);
        }
        BoolQueryBuilder queryBuilder = QueryBuilders.boolQuery();
        queryBuilder.must(QueryBuilders.matchQuery("all", key).operator(Operator.AND));
        Map<String, String> filters = request.getFilter();
        for (Map.Entry<String, String> filter : filters.entrySet()) {
            String name = filter.getKey();
            if ("分类".equals(name)) {
                name = "categoryId";
            } else if ("品牌".equals(name)) {
                name = "brandId";
            } else {
                name = "specs."+name;
            }
            String value = filter.getValue();
            queryBuilder.filter(QueryBuilders.termQuery(name,value));
        }

        return queryBuilder;
    }

    private void handleBrandAgg(Terms terms, Map<String, List<?>> filterList) {
        //解析bucket桶，获取ids
        List<Long> ids = terms.getBuckets().stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());
        //根据id查询品牌
        List<BrandDTO> list = itemClient.queryBrandByIds(ids);
        //存入返回数据
        filterList.put("品牌",list);
    }

    private List<Long> handleCategoryAgg(Terms terms, Map<String, List<?>> filterList) {
        //解析bucket桶，获取ids
        List<Long> ids = terms.getBuckets().stream().map(b -> b.getKeyAsNumber().longValue()).collect(Collectors.toList());
        //根据id查询品牌
        List<CategoryDTO> list = itemClient.queryCategoryByIds(ids);
        //存入返回数据
         filterList.put("分类",list);
         return ids;
    }

    public void insertIndex(Long spuId) {
        SpuDTO spuDTO = itemClient.querySpuById(spuId);

        Goods goods = buildGoods(spuDTO);

        goodsRepository.save(goods);
    }

    public void deleteIndex(Long spuId) {
        goodsRepository.deleteById(spuId);
    }
}
